package com.nulldev.util.web.nTube;

import java.io.ByteArrayInputStream;
import java.lang.reflect.Field;
import java.nio.charset.Charset;
import java.security.KeyStore;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;

import com.nulldev.util.JVM.Arguments;
import com.nulldev.util.JVM.Arguments.ParsedArguments;
import com.nulldev.util.JVM.JVM;
import com.nulldev.util.VariableAPI.ArrayUtils;
import com.nulldev.util.VariableAPI.MathUtil;
import com.nulldev.util.concurrency.threadIt.v4.emapi.ExecutorManager;
import com.nulldev.util.concurrency.threadIt.v4.emapi.uft.Executable;
import com.nulldev.util.concurrency.threadIt.v4.emapi.uft.Returnable;
import com.nulldev.util.data.Base64.Base64;
import com.nulldev.util.io.FastIO.FastIO;
import com.nulldev.util.io.file.FileUtil;
import com.nulldev.util.io.throttling.BandwidthThrottlerImpl;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.manage.Version;
import com.nulldev.util.manage.experiments.Experiments;
import com.nulldev.util.networking.ip.IP;
import com.nulldev.util.networking.misc.TLSUtil;
import com.nulldev.util.osAPI.OS;
import com.nulldev.util.web.HttpServer3.HttpServer3;
import com.nulldev.util.web.HttpServer3.Page;
import com.nulldev.util.web.HttpServer3.Response;
import com.nulldev.util.web.HttpServer3.SSLBehaviour;
import com.nulldev.util.web.HttpServer3.extensions.envStats.EMAPIStatsPage;
import com.nulldev.util.web.HttpServer3.extensions.envStats.ExperimentStatsPage;
import com.nulldev.util.web.HttpServer3.extensions.envStats.HTTPStatsPage;
import com.nulldev.util.web.HttpServer3.extensions.envStats.IOBuffersStatsPage;
import com.nulldev.util.web.HttpServer3.extensions.envStats.TCPDebugPage;
import com.nulldev.util.web.HttpServer3.pages.v3.HttpPage3;
import com.nulldev.util.web.HttpServer3.pages.v3.HttpPage3.CachingMethod;
import com.nulldev.util.web.HttpServer3.util.HttpServerCOEPPolicy;
import com.nulldev.util.web.HttpServer3.util.HttpServerCOOPPolicy;
import com.nulldev.util.web.HttpServer3.util.HttpServerCORSPolicy;
import com.nulldev.util.web.YouTubeExtractor.YouTubeExtractor;
import com.nulldev.util.web.nTube.NTubeLaunchArguments.nTubeArguments;
import com.nulldev.util.web.nTube.captcha.NTubeCaptchaResource;
import com.nulldev.util.web.nTube.components.video_queue.VideoQueueThread;
import com.nulldev.util.web.nTube.pages.invidious_compat.APIComments;
import com.nulldev.util.web.nTube.pages.invidious_compat.APIPopular;
import com.nulldev.util.web.nTube.pages.invidious_compat.APIStats;
import com.nulldev.util.web.nTube.pages.invidious_compat.APITrending;
import com.nulldev.util.web.nTube.pages.invidious_compat.APIVideos;
import com.nulldev.util.web.nTube.pages.proxying.YTChatProxy;
import com.nulldev.util.web.nTube.pages.proxying.YTDRMProxy;
import com.nulldev.util.web.nTube.resources.project_redwood.RedwoodResource;
import com.nulldev.util.web.nTube.util.INTubeInstance;
import com.nulldev.util.web.nTube.util.NTubeInstanceInfo;
import com.nulldev.util.web.nTubeExtensions.Redwood.Redwood;
import com.nulldev.util.web.nTubeExtensions.Redwood.pages.RedwoodBaseFile;
import com.nulldev.util.web.nTubeExtensions.Redwood.pages.RedwoodCaptionsFile;
import com.nulldev.util.web.nTubeExtensions.Redwood.pages.RedwoodPlayerCSSFile;
import com.nulldev.util.web.nTubeExtensions.Redwood.pages.RedwoodRemoteFile;
import com.nulldev.util.web.nTubeExtensions.Redwood.pages.RedwoodWebGLJSFile;

public class NTubeServer extends HttpServer3 implements INTubeInstance {

	private static final Logger log = LoggerFactory.getLogger();

	private static VideoQueueThread VIDEO_QUEUE;

	public static volatile boolean ENABLE_OPEN_PORTS = false;

	public static final boolean nTube_RELEASE_IS_LTS = false;
	public static final float nTube_RELEASE = 1.58f;

	public static final Version nTube_VERSION = new Version(1, 5, 9);

	/**
	 * The first build was in July 2018. (The YouTube Extractor originates from late
	 * 2017) <br />
	 * Builds between 1.0 and 1.4.4 weren't tracked, shame. <br />
	 * The final 2018 release is v1.4.4-rc1. (release-date: 29/12/2018) <br />
	 * The first 2019 release is v1.4.4-rc2. (release-date: 01/01/2019) <br />
	 * The second 2019 release is v1.4.4-rc3. (release-date: 06/01/2019) <br />
	 * The third 2019 release is v1.4.4-rc4. (release-date: 12/01/2019) <br />
	 * The fourth 2019 release is v1.4.4-rc5. (release-date: 17/01/2019) <br />
	 * The fifth 2019 release is v1.4.4 (release-date: 23/01/2019), this is the
	 * FINAL* 1.4.x release, the next release is 1.5.0. <br />
	 * * Excludes LTS releases (however, these only partially bring fixes from the
	 * latest version) <br />
	 * v1.5 development began on 14-02-2019 after nullUtil 3.0 beta 1 was reached.
	 * <br />
	 * During v1.5 development, an alpha 2.3.2-69-69-69-edition was released on
	 * April Fools. <br />
	 * v1.5-alpha finished on 29-08-2019. <br />
	 * v1.5-beta-0 came out on 30-08-2019. <br />
	 * v1.5-rc1-a1 came out on 01-12-2019. <br />
	 * v1.5-rc2-a1 came out on 01-01-2020. <br />
	 * v1.5-rc1 came out on 24-10-2020. <br />
	 * 1.5.0 came out on 31-12-2021. <br />
	 * 1.5.1 came out on 21-03-2021. <br />
	 * 1.5.2 came out on 31-03-2021. <br />
	 * 1.5.3 came out on 16-07-2022. <br />
	 * 1.5.4 came out on 24-01-2023. <br />
	 * 1.5.5 came out on 08-05-2023. <br />
	 * 1.5.6 came out on 14-07-2023. <br />
	 * 1.5.7 came out on 31-10-2023. <br />
	 * 1.5.8 came out on 15-03-2024.
	 */
	public static final String nTube_RELEASE_STRING = "v1.5.9";
	/**
	 * Codenames have been moved into the NTUBE_CODENAMES file in util.
	 */
	public static final String nTube_RELEASE_CODENAME = "does anyone even read this edition";
	public static final boolean DEVELOPMENT_BUILD = true;
	public static final int DEFAULT_PORT = 8080;

	private final NTubeInstanceInfo instanceInfo = new NTubeInstanceInfo();
	private Page redwood_captions_page, redwood_remote_page, redwood_webgl_js_page, invidious_compat_videos_page, invidious_compat_comments_page;

	// -Xshareclasses:cacheDir=/tmp/javasharedresources/

	public NTubeServer(final int PORT, final boolean RUN_PRIV_PORTS, final KeyManager[] km) throws Exception {
		this(NTubeOperationMode.DEFAULT, PORT, RUN_PRIV_PORTS, km);
	}

	public NTubeServer(final NTubeOperationMode mode, final int PORT, final boolean RUN_PRIV_PORTS, final KeyManager[] km) throws Exception {
		super(IP.freePort(PORT, PORT + 10), !ENABLE_OPEN_PORTS && Arguments.notArguments("--open", "-open"),
				RUN_PRIV_PORTS && PORT == DEFAULT_PORT ? SSLBehaviour.ATTEMPT_STANDARD
						: (Arguments.notAnArgument(nTubeArguments.DISABLE_SSL) ? SSLBehaviour.USE_NEAREST_PORT_POSITIVE : SSLBehaviour.DISABLED),
				km, (12 * 1024 * 1024)); // Use 12MB of cache on nTube
		log.traceEnabled.set(Arguments.hasArgument("--trace", "-trace"));
		log.debugEnabled.set(log.traceEnabled.get());
		this.flags().enable_sse = true;
		this.flags().enable_ws = true;
		this.isTimingDebugEnabled = Arguments.hasArgument("--HttpServer3.timingDebug");
		setServerName("nTube 1.5");
		/* Root */
		addDynamicAssetBytes("robots.txt", NTubeServer.class, "robots.txt", CachingMethod.VERY_LONG_CACHE, true);

		/* NTubePageLoader */
		final NTubePageLoader npl = new NTubePageLoader(this);
		switch (mode) {
			case INVIDIOUS_COMPAT_ONLY:
				/* No pages required. */
				break;
			case EMBED_ONLY:
				npl.loadEmbedded();
				break;
			case DEFAULT:
			default:
				npl.loadDefaults();
		}
		/* */
		final NTubeAPILoader apl = new NTubeAPILoader(this, VIDEO_QUEUE, PROXY_YT_DRM, PROXY_YT_CHAT);
		switch (mode) {
			case INVIDIOUS_COMPAT_ONLY:
				/* The API pages get loaded below. */
				apl.loadInvidiousOnly();
				break;
			case EMBED_ONLY:
				apl.loadEmbeddedOnly();
				break;
			case DEFAULT:
			default:
				apl.loadDefaults();
		}
		/* */

		/* nTube: Custom Captcha Page */
		if (Arguments.hasArgument("--HttpServer3.enableCaptcha")) {
			overrideFileString("captcha", FileUtil.getResourceAsString(NTubeCaptchaResource.class, "captcha.html"));
		}

		/* Project Redwood: Backend */
		deferredAddPage(new RedwoodBaseFile());
//		deferredAddPage(new RedwoodEmbedFile());
		deferredAddPage(() -> {
			try {
				return NTubeServer.this.redwood_captions_page = RedwoodCaptionsFile.make();
			} catch (Exception e) {
				log.error(e);
				return null;
			}
		});
		deferredAddPage(() -> {
			try {
				return NTubeServer.this.redwood_remote_page = RedwoodRemoteFile.make();
			} catch (Exception e) {
				log.error(e);
				return null;
			}
		});
		deferredAddPage(() -> {
			try {
				return NTubeServer.this.redwood_webgl_js_page = RedwoodWebGLJSFile.make();
			} catch (Exception e) {
				log.error(e);
				return null;
			}
		});
		deferredAddPage(() -> {
			if (!Redwood.ENABLE_AUTO_UPDATING) {
				addDynamicAssetBytes("/resources/redwood/player.css", RedwoodResource.class, "player.css", CachingMethod.LONG_CACHE);
				return null;
			} else {
				try {
					return RedwoodPlayerCSSFile.make();
				} catch (Exception e) {
					log.error(e);
					return null;
				}
			}
		});

		/* Invidious compatibility layer */
		final boolean enableInvidious = (NTubeLaunchArguments.get().has(NTubeLaunchArguments.nTubeArguments.ENABLE_INVIDIOUS_COMPAT)
				&& NTubeLaunchArguments.get().getValue(NTubeLaunchArguments.nTubeArguments.ENABLE_INVIDIOUS_COMPAT))
				|| mode == NTubeOperationMode.INVIDIOUS_COMPAT_ONLY;

		if (enableInvidious) {
			addPage(new APIStats());
			addPage(new APIPopular());
			addPage(new APITrending());
			deferredAddPage(() -> {
				try {
					return (this.invidious_compat_videos_page = new APIVideos());
				} catch (Exception ex) {
					log.error(ex);
					return null;
				}
			});
			deferredAddPage(() -> {
				try {
					return (this.invidious_compat_comments_page = new APIComments());
				} catch (Exception ex) {
					log.error(ex);
					return null;
				}
			});
		}

		if (DEVELOPMENT_BUILD) {
			addPage(new EMAPIStatsPage());
			addPage(new IOBuffersStatsPage());
			addPage(new TCPDebugPage());
			addPage(new HTTPStatsPage());
			addPage(new ExperimentStatsPage());
		}

		log.debug("Cache status: " + super.getCache());
		log.debug("HttpServer executor: " + super.getExecutor());
	}

	public static void main(String[] args) throws Exception {
		/**
		 * Start up messages have been moved into the NTUBE_START_UP_MESSAGES file in
		 * util.
		 */
		JVM.println("nTube.");
		JVM.println("Running: " + nTube_RELEASE_STRING + " (codename \"" + nTube_RELEASE_CODENAME + "\")");
		if (DEVELOPMENT_BUILD)
			log.warn("This is a development build, things MIGHT and WILL break.");
		NTubeLaunchArguments.init(args);
		final ParsedArguments launchArgs = NTubeLaunchArguments.get();
		final int PORT;
		if (launchArgs.has(NTubeLaunchArguments.nTubeArguments.port)) {
			PORT = MathUtil.clamp(80, 65535, launchArgs.getValue(NTubeLaunchArguments.nTubeArguments.port));
		} else {
			PORT = DEFAULT_PORT;
		}
		final boolean RUN_PRIV_PORTS = launchArgs.has(NTubeLaunchArguments.nTubeArguments.RUN_PRIV_PORTS)
				? launchArgs.getValue(NTubeLaunchArguments.nTubeArguments.RUN_PRIV_PORTS)
				: false;
		VIDEO_QUEUE = new VideoQueueThread();
		final Executable<Object> cinit = ExecutorManager.globalAsyncSubmit(new Returnable<Object>() {

			@Override
			public Object run() {
				if (RUN_PRIV_PORTS) {
					if (!OS.isPriv()) {
						log.error("nTube: You have privileged ports enabled, but aren't running as a privileged account. Exiting.");
						FastIO.get().flush();
						System.exit(-1);
						return null;
					} else {
						log.warn("nTube: Running as root/admin, this is not recommended.");
					}
				} else if (OS.isPriv()) {
					log.error("nTube: You are running as root/admin without privileged ports being enabled. Exiting for your own safety.");
					FastIO.get().flush();
					System.exit(-1);
					return null;
				}
				if (OS.isWindows() && JVM.version() < 18f) {
					log.warn("Enforcing UTF-8 mode for Windows. [NTUBE_QUIRKS_UTF8_TEXT_WINDOWS]");
					System.setProperty("file.encoding", "UTF-8");
					try {
						Field charset = Charset.class.getDeclaredField("defaultCharset");
						charset.setAccessible(true);
						charset.set(null, null);
					} catch (Exception e) {
						log.error("Failed to update global charset!", e);
					}
				} else if (OS.isMacOS()) {
					log.warn("macOS is untested and comes with near-zero support!");
				}
				return null;
			}
		});
		Executable<KeyManager[]> _keyManagerTask = null;
		if (Arguments.notAnArgument(nTubeArguments.DISABLE_SSL)) {
			_keyManagerTask = ExecutorManager.globalAsyncSubmit(new Returnable<KeyManager[]>() {

				@Override
				public KeyManager[] run() {
					try {
						try {
							final String[] protocols = SSLContext.getDefault().getSupportedSSLParameters().getProtocols();
							System.setProperty("https.protocols", ArrayUtils.join(protocols, ","));
						} catch (Exception e) {
							if (JVM.version() >= 11f) {
								System.setProperty("https.protocols", "TLSv1,TLSv1.1,TLSv1.2,TLSv1.3");
							} else {
								System.setProperty("https.protocols", "TLSv1,TLSv1.1,TLSv1.2");
							}
						}
						if (launchArgs.has(NTubeLaunchArguments.nTubeArguments.SSL_CERTIFICATE_PATH)) {
							final boolean useECDSAKey = launchArgs.has(NTubeLaunchArguments.nTubeArguments.SSL_USE_ECDSA_PRIVATE_KEY)
									&& launchArgs.getValue(NTubeLaunchArguments.nTubeArguments.SSL_USE_ECDSA_PRIVATE_KEY);
							final String path = launchArgs.getValue(NTubeLaunchArguments.nTubeArguments.SSL_CERTIFICATE_PATH);
							if (path.contains(";")) {
								final String[] paths = path.split(";");
								if (paths.length != 2)
									throw new IllegalArgumentException("2 files are required [cert pem, private key pem]");
								final String certPem = paths[0];
								final String keyPem = paths[1];
								log.info("[SSL] Loading seperate cert and key files.");
								return TLSUtil.getKeyManagers(certPem, keyPem, useECDSAKey);
							} else {
								log.info("[SSL] Loading single cert and key file.");
								return TLSUtil.getKeyManagers(path, useECDSAKey);
							}
						} else {
							log.info("[SSL] Using nullUtil's fallback cert for nTube.");
							final KeyStore ks = KeyStore.getInstance("JKS");
							ks.load(new ByteArrayInputStream(Base64.decodeBase64(JKS_SSL_CONTAINER)), "changeit".toCharArray() /* Make me. */);
							final KeyManagerFactory km = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
							km.init(ks, "ntube_1_5_basic_ssl".toCharArray()); // Very Secure, much wow.
							return km.getKeyManagers();
						}
					} catch (Exception e) {
						JVM.errorln(e);
						return null;
					}
				}
			});
		}
		final Executable<KeyManager[]> keyManagerTask = _keyManagerTask;
		if (Arguments.notAnArgument(nTubeArguments.DISABLE_PRECACHING)) {
			log.debug("[v8] Caching signature decipherer...");
			ExecutorManager.get().asyncSubmit(new Runnable() {

				@Override
				public void run() {
					try {
						YouTubeExtractor.extractor.getAuto();
						/* XXX: We should also cache v9 here. */
					} catch (Exception e) {
						log.error(e);
					}
				}
			});
		}

		final NTubeOperationMode operationMode = NTubeLaunchArguments.getOperationMode(launchArgs);
		log.info("Configuring instance for " + operationMode + " operation.");

		if (Arguments.notArguments("--NTubeServer.avoidMainSingleThread")) {
			log.info("Launching via main thread.");
			cinit.waitTillCompletion();
			final NTubeServer nst = new NTubeServer(operationMode, PORT, RUN_PRIV_PORTS, keyManagerTask == null ? null : keyManagerTask.waitTillCompletion());
			nst.settings().enableNoDDOS = Arguments.notArguments("--NTubeServer.disableDDOSProtection");
			nst.settings().enableBotAway = Arguments.hasArgument("--NTubeServer.enableBotAway");
			nst.settings().enableCrawlerBlock = Arguments.hasArgument("--NTubeServer.enableCrawlerBlock");
			nst.settings().enableHSTS = Arguments.hasArgument("--NTubeServer.enableHSTS");
			nst.settings().enableHSTSForSubdomains = Arguments.hasArgument("--NTubeServer.enableHSTSForSubdomains");
			nst.settings().enableProxyBotStop = Arguments.hasArgument("--NTubeServer.enablePBS");
			nst.settings().debugPrintRequests = Arguments.hasArgument("--NTubeServer.printRequests");
			nst.settings().debugPrintResponses = Arguments.hasArgument("--NTubeServer.printResponses");
			nst.isTimingDebugEnabled = Arguments.hasArgument("--NTubeServer.timingDebug");
			nst.logger().debugEnabled.set(Arguments.hasArgument("--NTubeServer.timingDebug", "--NTubeServer.enableHttpServerDebugging"));
			if (Arguments.hasArgument("--NTubeServer.enableSecurityHardening")) {
				nst.settings().corsPolicy = HttpServerCORSPolicy.SAME_ORIGIN;
				nst.settings().coepPolicy = HttpServerCOEPPolicy.UNSAFE_NONE;
				nst.settings().coopPolicy = HttpServerCOOPPolicy.SAME_ORIGIN_ALLOW_POPUPS;
			}
			if (Arguments.hasArgument("--NTubeServer.basicThrottling")) {
				nst.settings().bandwidthThrottler = new BandwidthThrottlerImpl(80000000 /* 10 MBps */);
				log.info("Bandwidth throttling active: upstream: N/A, downstream: 10 MB/s");
			}
			if (Arguments.hasArgument("--NTubeServer.runGCAfterInit")) {
				ExecutorManager.get().asyncSubmit(new Runnable() {

					@Override
					public void run() {
						System.gc();
						log.trace("Ran JVM GC.");
					}
				});
			}
			try {
				NTubeInstanceInfo.parseAndSet(launchArgs, nst);
			} catch (Exception ex) {
				JVM.errorln(ex);
			}
		} else {
			log.info("Launching via queued task.");
			ExecutorManager.get().asyncSubmit(new Runnable() {

				@Override
				public void run() {
					try {
						cinit.waitTillCompletion();
						final NTubeServer nst = new NTubeServer(operationMode, PORT, RUN_PRIV_PORTS,
								keyManagerTask == null ? null : keyManagerTask.waitTillCompletion());
						nst.settings().enableNoDDOS = Arguments.notArguments("--NTubeServer.disableDDOSProtection");
						nst.settings().enableBotAway = Arguments.hasArgument("--NTubeServer.enableBotAway");
						nst.settings().enableCrawlerBlock = Arguments.hasArgument("--NTubeServer.enableCrawlerBlock");
						nst.settings().enableHSTS = Arguments.hasArgument("--NTubeServer.enableHSTS");
						nst.settings().enableHSTSForSubdomains = Arguments.hasArgument("--NTubeServer.enableHSTSForSubdomains");
						nst.settings().enableProxyBotStop = Arguments.hasArgument("--NTubeServer.enablePBS");
						nst.settings().debugPrintRequests = Arguments.hasArgument("--NTubeServer.printRequests");
						nst.settings().debugPrintResponses = Arguments.hasArgument("--NTubeServer.printResponses");
						nst.isTimingDebugEnabled = Arguments.hasArgument("--NTubeServer.timingDebug");
						nst.logger().debugEnabled.set(Arguments.hasArgument("--NTubeServer.timingDebug", "--NTubeServer.enableHttpServerDebugging"));
						if (Arguments.hasArgument("--NTubeServer.enableSecurityHardening")) {
							nst.settings().corsPolicy = HttpServerCORSPolicy.SAME_ORIGIN;
							nst.settings().coepPolicy = HttpServerCOEPPolicy.UNSAFE_NONE;
							nst.settings().coopPolicy = HttpServerCOOPPolicy.SAME_ORIGIN_ALLOW_POPUPS;
						}
						if (Arguments.hasArgument("--NTubeServer.basicThrottling")) {
							nst.settings().bandwidthThrottler = new BandwidthThrottlerImpl(80000000 /* 10 MBps */);
							log.info("Bandwidth throttling active: upstream: N/A, downstream: 10 MB/s");
						} else if (launchArgs.has(NTubeLaunchArguments.nTubeArguments.THROTTLING)) {
							final int bandwidthLimit = launchArgs.getValue(NTubeLaunchArguments.nTubeArguments.THROTTLING);
							if (bandwidthLimit <= 1024) {
								log.warn("Invalid bandwidth throttling limit, skipped.");
							} else {
								nst.settings().bandwidthThrottler = new BandwidthThrottlerImpl(bandwidthLimit);
								log.info("Bandwidth throttling active: upstream: N/A, downstream: " + FileUtil.convertToStringRepresentation(bandwidthLimit));
							}
						}
						if (Arguments.hasArgument("--NTubeServer.runGCAfterInit")) {
							System.gc();
							log.trace("Ran GC layer.");
						}
						try {
							NTubeInstanceInfo.parseAndSet(launchArgs, nst);
						} catch (Exception ex) {
							JVM.errorln(ex);
						}
					} catch (Exception e) {
						JVM.errorln(e);
					}
				}
			});
		}
		if (Experiments.get("ntube://enable-fio-exceptions/").isActive()) {
			try {
				Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {

					@Override
					public void uncaughtException(Thread thread, Throwable throwable) {
						if (throwable == null)
							return;
						JVM.errorln(throwable);
					}
				});
			} catch (Exception e) {
				JVM.errorln(e);
			}
		}
	}

	private static final HttpPage3 PROXY_YT_CHAT = new YTChatProxy();
	private static final HttpPage3 PROXY_YT_DRM = new YTDRMProxy();
	private static final HttpPage3 BLOCKED_REQUEST = new HttpPage3("/ntube_blocked") {

		@Override
		public Response onRequest(Request request) {
			return new Response(200);
		}
	};

	private static final String[] BLOCKED_PASSTHROUGH_REQUESTS =
		{ "/youtubei/v1/log_event", "/error_204", "/youtubei/v1/feedback", "/api/stats/atr", "/yt-live-chat-default-theme.css.map" };
	private static final String[] EXTRA_CHAT_PROXY_REQUESTS =
		{ "/yts/", "/live_chat/get_live_chat", "/live_chat_push_config_ajax", "/youtubei/v1/live_chat/get_live_chat",
				"/youtubei/v1/live_chat/get_item_context_menu" };

	@Override
	public Page unknown(final String id, final Object sock) {
		// TODO: Move stuff into caches and register these pages.
		if (id.startsWith("/embed/")) {
			return this.getPage("/embed", sock);
		} else if (id.startsWith("/b64_dec.mpd")) {
			return this.getPage("/b64_dec", sock);
		} else if (ArrayUtils.StringArrayUtils.containsStartsWithInverse(EXTRA_CHAT_PROXY_REQUESTS, id)) {
			return PROXY_YT_CHAT;
		} else if (id.startsWith("/youtubei/v1/player/get_drm_license")) {
			return PROXY_YT_DRM;
		} else if (ArrayUtils.StringArrayUtils.containsStartsWithInverse(BLOCKED_PASSTHROUGH_REQUESTS, id)) {
			return BLOCKED_REQUEST;
		} else if (id.startsWith("/s/player/") && id.endsWith("/captions.js")) {
			return this.redwood_captions_page;
		} else if (id.startsWith("/s/player/") && id.endsWith("/remote.js")) {
			return this.redwood_remote_page;
		} else if (id.startsWith("/s/player/") && id.endsWith("/webgl.js")) {
			return this.redwood_webgl_js_page;
		} else if (id.startsWith("/api/v1/videos/")) {
			return this.invidious_compat_videos_page;
		} else if (id.startsWith("/api/v1/comments/")) {
			return this.invidious_compat_comments_page;
		} else {
			log.error("unknown() -> " + id + " [" + sock + "]");
			return null;
		}
	}

	@Override
	public NTubeInstanceInfo getInstanceInfo() {
		return this.instanceInfo;
	}

}
